using System;
using System.Collections;
using System.Xml;
using System.IO;

namespace FridayShopping.DataModel
{
	/// <summary>
	/// Abstract class that represents the list of items. It's the base class 
	/// for all the lists in the data model.
	/// We keep the next key to be assigne as a unique key of the item and a list
	/// of items. Each item in the list has an attribute attached to it. 
	/// We also keep hashtable updated at all times for quick reference from the key
	/// to the actual item
	/// </summary>
	public abstract class ItemListBase: IItemList
	{
		private		Key			nextKey;

		private		ArrayList	list;

		private		Hashtable	hashTable;

		#region Constructor
		public ItemListBase() 
		{
		}
		#endregion

		#region Member Access
		public Key NextKey 
		{
			get 
			{
				return nextKey;
			}
			set 
			{
				nextKey = value;
			}
		}

		virtual public ArrayList List 
		{
			get
			{
				if (list == null) 
				{
					list = new ArrayList();
				}
				return list;
			}
			set 
			{
				list = value;
			}
		}

		private Hashtable HashTable 
		{
			get
			{
				if (hashTable == null) 
				{
					hashTable = new Hashtable();
				}
				return hashTable;
			}
			set
			{
				hashTable = value;
			}
		}
		#endregion

		#region Opeation on list (Get/Add/Delete/MoveUp/Down) a record
		public Key Add(ItemBase item)
		{
			item.Key = NextKey;
			List.Add(item);
			HashTable[item.Key] = item;
			NextKey = NextKey.GetNextKey();
			return item.Key;
		}

		public void AddWithKey(ItemBase item) 
		{
			List.Add(item);
			HashTable[item.Key] = item;
		}

		public void Delete(Key key)
		{
			ItemBase item = (ItemBase)HashTable[key];
			if (item == null) 
			{
				throw new ItemException("Item key was not found");
			}
			List.Remove(item);
			HashTable[key] = null;
		}

		public void MoveUp(ItemBase item)
		{
			int index = List.IndexOf(item);
			if (index < 0) 
			{
				throw new ItemException("Item was not found");
			}
			// No where to move up already topmost
			if (index == 0) 
			{
				return;
			}
			List.RemoveAt(index);
			List.Insert(index-1, item);
		}

		public void MoveDown(ItemBase item)
		{
			int index = List.IndexOf(item);
			if (index < 0) 
			{
				throw new ItemException("Item was not found");
			}
			// No where to move down already at bottom
			if (index == List.Count - 1) 
			{
				return;
			}
			List.RemoveAt(index);
			List.Insert(index + 1, item);
		}

		public ItemBase GetItem(Key key)
		{
			return (ItemBase)HashTable[key];
		}

		private Type CreateListItemInstance(Type listType)
		{
			ListItemTypeAttribute[] attrs = 
				(ListItemTypeAttribute[]) listType.GetCustomAttributes(typeof(ListItemTypeAttribute), true);

			if (attrs.Length > 0) 
			{
				return attrs[0].ItemType;
			} 
			else 
			{
				return null;
			}
		}

		private void UpdateHashTable() 
		{
			HashTable.Clear();

			foreach (ItemBase item in List) 
			{
				HashTable[item.Key] = item;
			}
		}
		#endregion

		#region Serialize/Deserialize
		public void Save(string fileName)
		{
			TextWriter writer = new StreamWriter(fileName);
			
			XmlTextWriter xmlWriter = new XmlTextWriter(writer);

			this.Serialize(xmlWriter);

			writer.Close();
		}

		public void Serialize(XmlTextWriter xmlWriter, string elementName)
		{
			xmlWriter.WriteStartElement(elementName);
			
			SerializeMembers(xmlWriter);
			
			xmlWriter.WriteEndElement();
		}

		public void Serialize(XmlTextWriter xmlWriter)
		{
			Serialize(xmlWriter, GetType().Name);
		}

		protected virtual void SerializeMembers(XmlTextWriter xmlWriter)
		{
			NextKey.Serialize(xmlWriter, "NextKey");

			foreach(ItemBase item in List)
			{
				item.Serialize(xmlWriter);
			}
		}

		static public ItemListBase Load(Type type, string fileName)
		{

			XmlDocument doc = new XmlDocument();

			try 
			{
				doc.Load(fileName);
			} 
			catch (IOException e) 
			{
				throw new DatabaseException("Failed to load " + fileName, e);
			}
			catch (XmlException e) 
			{
				throw new DatabaseException("Failed to parse file " + fileName, e);
			}

			ItemListBase rc = (ItemListBase)Activator.CreateInstance(type);
			
			// Read the root element (Shops, Items, etc..)

			XmlNodeReader xmlReader = new XmlNodeReader(doc);
			xmlReader.ReadStartElement();
			try 
			{
				rc.DeserializeMembers(xmlReader);
			}
			catch (XmlException e) 
			{
				throw new DatabaseException("Failed to parse xml element", e);
			}

			rc.UpdateHashTable();

			return rc;
		}

		public virtual void DeserializeMembers(XmlNodeReader xmlReader)
		{

			// Read NextKey
			NextKey = Key.DeserializeMembers(xmlReader);
		
			// Read the items			
			Type itemType = CreateListItemInstance(GetType());

			if (itemType == null) 
			{
				throw new ItemException("Failed to create list item type for " + GetType().FullName);
			}
				
			// While reading the type of this list, loop
			while (xmlReader.IsStartElement(itemType.Name)) 
			{
				ItemBase item = (ItemBase)Activator.CreateInstance(itemType);

				// Read one item
				xmlReader.ReadStartElement();
				item.DeserializeMembers(xmlReader);
				xmlReader.ReadEndElement();

				// Add to list of items
				List.Add(item);
			}

			xmlReader.ReadEndElement();
		}
		#endregion

	}

	/// <summary>
	/// Throw this exception when there is a problem with reading/writing/
	/// getting an item from within the list
	/// </summary>
	public class ItemException : ApplicationException 
	{
		#region Constructors
		public ItemException() : base()
		{			
		}

		public ItemException(string msg) : base (msg) 
		{
		}

		public ItemException(string msg, Exception inner) : base (msg, inner) 
		{
		}
		#endregion
	}

}
